/*
 * Copyright 2015-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.lijiajia3515.cairo.auth.framework.security.jackson2;

import cn.lijiajia3515.cairo.auth.framework.security.CairoAuthAccount;
import cn.lijiajia3515.cairo.domain.CairoAccount;
import cn.lijiajia3515.cairo.domain.CairoDepartment;
import cn.lijiajia3515.cairo.domain.CairoRole;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Custom Deserializer for {@link User} class. This is already registered with
 * {@link CairoAuthAccountMixin}. You can also use it directly with your mixin class.
 *
 * @author Jitendra Singh
 * @see CairoAuthAccountMixin
 * @since 4.2
 */
class CairoAuthUserDeserializer extends JsonDeserializer<CairoAuthAccount> {

	private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
	};
	private static final TypeReference<CairoAccount> CAIRO_ACCOUNT = new TypeReference<CairoAccount>() {
	};

	/**
	 * This method will create {@link CairoAuthAccount} object. It will ensure successful object
	 * creation even if password key is null in serialized json, because credentials may
	 * be removed from the {@link CairoAuthAccount} by invoking In
	 * that case there won't be any password key in serialized json.
	 *
	 * @param jp   the JsonParser
	 * @param ctxt the DeserializationContext
	 * @return the user
	 * @throws IOException             if a exception during IO occurs
	 * @throws JsonProcessingException if an error during JSON processing occurs
	 */
	@Override
	public CairoAuthAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		ObjectMapper mapper = (ObjectMapper) jp.getCodec();
		JsonNode jsonNode = mapper.readTree(jp);
		Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
			SIMPLE_GRANTED_AUTHORITY_SET);
		CairoAccount account = mapper.convertValue(jsonNode, CAIRO_ACCOUNT);

		JsonNode passwordNode = readJsonNode(jsonNode, "password");
		String password = passwordNode.asText("");

		boolean accountExpired = readJsonNode(jsonNode, "accountExpired").asBoolean();
		boolean accountEnabled = readJsonNode(jsonNode, "accountEnabled").asBoolean();
		boolean accountLocked = readJsonNode(jsonNode, "accountLocked").asBoolean();
		boolean credentialsExpired = readJsonNode(jsonNode, "credentialsExpired").asBoolean();

		CairoAuthAccount result = CairoAuthAccount.builder()
			.authorities(authorities)
			.password(password)
			.id(account.getId())
			.login(account.getLogin())
			.phoneNumber(account.getPhoneNumber())
			.email(account.getEmail())
			.nickname(account.getNickname())
			.avatarUrl(account.getAvatarUrl())
			.roles(account.getRoles())
			.departments(account.getDepartments())

			.accountEnabled(accountEnabled)
			.accountLocked(accountLocked)
			.accountExpired(accountExpired)
			.credentialsExpired(credentialsExpired)
			.build();

		if (passwordNode.asText(null) == null) {
			result.eraseCredentials();
		}

		return result;
	}

	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
	}

	private List<CairoRole> readCairoRole(JsonNode node) {
		List<CairoRole> roles = new ArrayList<>();
		if (node.isArray()) {
			node.forEach(x -> {
				String id = readJsonNode(x, "id").asText();
				String name = readJsonNode(x, "name").asText();
				roles.add(CairoRole.builder().id(id).name(name).build());
			});
		}
		return roles;
	}

	private List<CairoDepartment> readCairoDepartment(JsonNode node) {
		List<CairoDepartment> department = new ArrayList<>();
		if (node.isArray()) {
			node.forEach(x -> {
				String id = readJsonNode(x, "id").asText();
				String name = readJsonNode(x, "name").asText();
				department.add(CairoDepartment.builder().id(id).name(name).build());
			});
		}
		return department;
	}

}
